/*
	Copyright (c) 2012 Petter Hansson
	This source code is licensed under The MIT License,
	see the file LICENSE for details.
	*/

/*
	Provides a table-oriented lua_State wrapper.
	*/

#ifndef LUAENGINE_HPP
#define LUAENGINE_HPP

#include "lua/lua.hpp"

#include <functional>
#include <string>
#include <vector>

#include "HelpDefs.hpp"

namespace spelprog2 {
namespace lua {

// Represents Lua's nil in the templates.
struct Nil {
};

template <class En>
class LuaRefT {
protected:
	struct Data { //poor name in hindsight, not to be confused with data of the Lua table pointed at
		En * en;
		int index;
		int refcount;
	} * data;

	LuaRefT() : data(nullptr) {
	}

	LuaRefT(En * en) : data(new Data()) {
		data->refcount = 1;
		data->en = en;
		data->index = luaL_ref(en->lua, LUA_REGISTRYINDEX);
	}

	void deref() {
		data->refcount -= 1;
		if (data->refcount == 0) {
			luaL_unref(data->en->lua, LUA_REGISTRYINDEX, data->index);
			delete data;
		}
	}
public:
	LuaRefT(const LuaRefT & other) : data(other.data) {
		data->refcount += 1;
	}

	LuaRefT & operator =(const LuaRefT & other) {
		if (data != other.data) {
			if (data) {
				deref();
			}

			data = other.data;
			if (data) {
				data->refcount += 1;
			}
		}
	}

	virtual ~LuaRefT() {
		if (data) {
			deref();
		}
	}

	int getRef() {
		typename En * en = data->en;
		lua_rawgeti(en->lua, LUA_REGISTRYINDEX, data->index);

		return lua_gettop(en->lua);
	}
};

// Lua function ===============================================================

template <class En>
class FunctionT : public LuaRefT<En> {
public:
	FunctionT() : LuaRefT<En>() {
	}

	FunctionT(En * en) : LuaRefT(en) {
	}

	// Calling Lua code =======================================================

	//primitive VA template emulation, MSVC not supporting VA templates at time of writing

	template <class RetT,
		class A, class B, class C, class D, class E, class F>
	RetT call(A a, B b, C c, D d, E e, F f) {
		getRef();

		typename En * en = data->en;
		en->push(a); en->push(b); en->push(c); en->push(d); en->push(e); en->push(f);

		return en->pcall<RetT>(6);
	}

	template <class RetT,
		class A, class B, class C, class D, class E>
	RetT call(A a, B b, C c, D d, E e) {
		getRef();

		typename En * en = data->en;
		en->push(a); en->push(b); en->push(c); en->push(d); en->push(e);

		return en->pcall<RetT>(5);
	}

	template <class RetT,
		class A, class B, class C, class D>
	RetT call(A a, B b, C c, D d) {
		getRef();

		typename En * en = data->en;
		en->push(a); en->push(b); en->push(c); en->push(d);

		return en->pcall<RetT>(4);
	}

	template <class RetT,
		class A, class B, class C>
	RetT call(A a, B b, C c) {
		getRef();

		typename En * en = data->en;
		en->push(a); en->push(b); en->push(c);

		return en->pcall<RetT>(3);
	}

	template <class RetT,
		class A, class B>
	RetT call(A a, B b) {
		getRef();

		typename En * en = data->en;
		en->push(a); en->push(b);

		return en->pcall<RetT>(2);
	}

	template <class RetT,
		class A>
	RetT call(A a) {
		getRef();

		typename En * en = data->en;
		en->push(a);

		return en->pcall<RetT>(1);
	}

	template <class RetT>
	RetT call() {
		getRef();

		typename En * en = data->en;

		return en->pcall<RetT>(0);
	}
};

// Lua table ==================================================================
template <class En> //class template due to circular dependency issues
class TableT : public LuaRefT<En> {
	template <class IndexT>
	void getElem(IndexT index) {
		typename En * en = data->en;
		int ref = getRef();
		en->push(index);
		lua_gettable(en->lua, ref);
	}

	template <class ValT, class IndexT>
	void setElem(IndexT index, ValT val) {
		typename En * en = data->en;
		int ref = getRef();
		en->push(index);
		en->push(val);
		lua_settable(en->lua, ref);
	}
public:
	TableT() : LuaRefT<En>() {
	}

	TableT(En * en) : LuaRefT(en) {
	}

	// Calling Lua code =======================================================

	//primitive VA template emulation, MSVC not supporting VA templates at time of writing

	template <class RetT, class IndexT,
		class A, class B, class C, class D, class E, class F>
	RetT call(IndexT index, A a, B b, C c, D d, E e, F f) {
		getElem(index);

		typename En * en = data->en;
		en->push(a); en->push(b); en->push(c); en->push(d); en->push(e); en->push(f);

		return en->pcall<RetT>(6);
	}

	template <class RetT, class IndexT,
		class A, class B, class C, class D, class E>
	RetT call(IndexT index, A a, B b, C c, D d, E e) {
		getElem(index);

		typename En * en = data->en;
		en->push(a); en->push(b); en->push(c); en->push(d); en->push(e);

		return en->pcall<RetT>(5);
	}

	template <class RetT, class IndexT,
		class A, class B, class C, class D>
	RetT call(IndexT index, A a, B b, C c, D d) {
		getElem(index);

		typename En * en = data->en;
		en->push(a); en->push(b); en->push(c); en->push(d);

		return en->pcall<RetT>(4);
	}

	template <class RetT, class IndexT,
		class A, class B, class C>
	RetT call(IndexT index, A a, B b, C c) {
		getElem(index);

		typename En * en = data->en;
		en->push(a); en->push(b); en->push(c);

		return en->pcall<RetT>(3);
	}

	template <class RetT, class IndexT,
		class A, class B>
	RetT call(IndexT index, A a, B b) {
		getElem(index);

		typename En * en = data->en;
		en->push(a); en->push(b);

		return en->pcall<RetT>(2);
	}

	template <class RetT, class IndexT,
		class A>
	RetT call(IndexT index, A a) {
		getElem(index);

		typename En * en = data->en;
		en->push(a);

		return en->pcall<RetT>(1);
	}

	template <class RetT, class IndexT>
	RetT call(IndexT index) {
		getElem(index);
		return en->pcall<RetT>(0);
	}

	// Accessors ==============================================================

	template <class RetT, class IndexT>
	RetT get(IndexT index) {
		getElem(index);
		return en->pop<RetT>();
	}

	template <class ValT, class IndexT>
	void set(IndexT index, ValT val) {
		setElem(index, val);
	}
};

// Lua "engine"/wrapper =======================================================
class Engine {
	const bool owned; //is lua state owned by Engine?
public:
	lua_State * lua; //really, can't hide this everywhere. also user might need to use, although that shouldn't normally be the case

	typedef TableT<Engine> Table;
	typedef FunctionT<Engine> Function;

	// Init/Setup ==============================================================

	Engine(lua_State * state) : owned(false), lua(state) {
	}

	Engine() : owned(true), lua(luaL_newstate()) {
		if (lua == nullptr) {
			throw "Lua could not be initialized";
		}
		
		luaL_openlibs(lua);
	}

	~Engine() {
		if (owned) {
			lua_close(lua);
		}
	}

	// Execution.

	void dofile(const char * fname) {
		if (luaL_loadfile(lua, fname) == LUA_OK) {
			pcall<void>(0);
		}
		else {
			gamelog << "Lua dofile error: " << lua_tostring(lua, -1) << '\n';
			lua_pop(lua, 1);
			throw "Lua file could not be performed";
		}
	}

	void dofile(const std::string & fname) {
		dofile(fname.c_str());
	}

	void dostring(const char * str) {
		if (luaL_loadstring(lua, str) == LUA_OK) {
			pcall<void>(0);
		}
		else {
			gamelog << "Lua dostring error: " << lua_tostring(lua, -1) << '\n';
			lua_pop(lua, 1);
			
			throw "Lua string could not be performed";
		}
	}

	void dostring(const std::string & str) {
		dostring(str.c_str());
	}

	// Table management.

	Table getGlobals() {
		lua_getglobal(lua, "_G");
		return Table(this);
	}

	Table newTable() {
		lua_newtable(lua);
		return Table(this);
	}

	// Function registration.

	template <class RetT, class A, class B, class C, class D, class E, class F>
	Function newFunction(std::function<RetT (A, B, C, D, E, F)> func) {
		return wrapFunction(func, cCall<decltype(func), RetT, A, B, C, D, E, F>);
	}

	template <class RetT, class A, class B, class C, class D, class E>
	Function newFunction(std::function<RetT (A, B, C, D, E)> func) {
		return wrapFunction(func, cCall<decltype(func), RetT, A, B, C, D, E>);
	}

	template <class RetT, class A, class B, class C, class D>
	Function newFunction(std::function<RetT (A, B, C, D)> func) {
		return wrapFunction(func, cCall<decltype(func), RetT, A, B, C, D>);
	}

	template <class RetT, class A, class B, class C>
	Function newFunction(std::function<RetT (A, B, C)> func) {
		return wrapFunction(func, cCall<decltype(func), RetT, A, B, C>);
	}

	template <class RetT, class A, class B>
	Function newFunction(std::function<RetT (A, B)> func) {
		return wrapFunction(func, cCall<decltype(func), RetT, A, B>);
	}

	template <class RetT, class A>
	Function newFunction(std::function<RetT (A)> func) {
		return wrapFunction(func, cCall<decltype(func), RetT, A>);
	}

	template <class RetT>
	Function newFunction(std::function<RetT ()> func) {
		return wrapFunction(func, cCall<decltype(func), RetT>);
	}

	// Templated push. (Don't normally use in user code.)

	void push(void) {
		//do nothing. function exists to be able to use default template arguments sensibly
	}

	void push(Nil) {
		lua_pushnil(lua);
	}

	void push(bool b) {
		lua_pushboolean(lua, b? 1 : 0);
	}

	void push(int n) { //TODO: handle numbers via templates if possible, to auto convert to lua_Num/double
		lua_pushnumber(lua, static_cast<lua_Number>(n));
	}

	void push(double n) {
		lua_pushnumber(lua, static_cast<lua_Number>(n));
	}

	void push(const char * str) {
		lua_pushstring(lua, str);
	}

	void push(const std::string & str) {
		lua_pushstring(lua, str.c_str());
	}

	void push(Table & table) {
		table.getRef();
	}

	void push(Function & function) {
		function.getRef();
	}

	// Templated pop. (Don't normally use in user code.)

	void pop(Nil) {
		//TODO: typecheck, as for all other pops
		lua_pop(lua, 1);
	}

	void pop(bool & b) {
		b = lua_toboolean(lua, -1) != 0;
		lua_pop(lua, 1);
	}

	void pop(int & n) {
		n = static_cast<int>(lua_tointeger(lua, -1));
		lua_pop(lua, 1);
	}

	void pop(double & n) {
		n = static_cast<double>(lua_tonumber(lua, -1));
		lua_pop(lua, 1);
	}

	void pop(std::string & str) {
		str = lua_tostring(lua, -1);
		lua_pop(lua, 1);
	}

	void pop(Table & table) {
		table = Table(this);
	}

	void pop(Function & function) {
		function = Function(this);
	}

	template <class RetT> //for convenience with other templates
	RetT pop() {
		RetT r;
		pop(r);
		return r;
	}

	// Pcall ==================================================================

	template <class RetT>
	RetT pcall(int nargs) {
		if (lua_pcall(lua, nargs, 1, 0) == LUA_OK) {
			return pop<RetT>();
		}
		else {
			gamelog << "Lua function call error: " << lua_tostring(lua, -1) << '\n';
			lua_pop(lua, 1);
			return RetT();
		}
	}

	template <> //specialization for void -> don't want to try reference to void ;)
	void pcall(int nargs) {
		if (lua_pcall(lua, nargs, 0, 0) != LUA_OK) {
			gamelog << "Lua function call error: " << lua_tostring(lua, -1) << '\n';
			lua_pop(lua, 1);
		}
	}

	//TODO: var return arguments version via manual popping

private:
	// Function binding internals =============================================

	template <class T>
	struct IsVoid {
	  static const bool value = false;
	};

	template <>
	struct IsVoid<void> {
	  static const bool value = true;
	};

	template <class FunT, class RetT,
		class A, class B, class C, class D, class E, class F>
	static int __cdecl cCall(lua_State * state) {
		auto func = reinterpret_cast<FunT *>(lua_touserdata(state, lua_upvalueindex(1)));

		Engine en(state);
		A a; B b; C c; D d; E e; F f;
		en.pop(a); en.pop(b); en.pop(c); en.pop(d); en.pop(e); en.pop(f);

		if (IsVoid<RetT>::value) {
			(*func)(a, b, c, d, e, f);
			return 0;
		}
		else {
			en.push((*func)(a, b, c, d, e, f));
			return 1;
		}
	}

	template <class FunT, class RetT,
		class A, class B, class C, class D, class E>
	static int __cdecl cCall(lua_State * state) {
		auto func = reinterpret_cast<FunT *>(lua_touserdata(state, lua_upvalueindex(1)));

		Engine en(state);
		A a; B b; C c; D d; E e;
		en.pop(a); en.pop(b); en.pop(c); en.pop(d); en.pop(e);

		if (IsVoid<RetT>::value) {
			(*func)(a, b, c, d, e);
			return 0;
		}
		else {
			en.push((*func)(a, b, c, d, e));
			return 1;
		}
	}

	template <class FunT, class RetT,
		class A, class B, class C, class D>
	static int __cdecl cCall(lua_State * state) {
		auto func = reinterpret_cast<FunT *>(lua_touserdata(state, lua_upvalueindex(1)));

		Engine en(state);
		A a; B b; C c; D d;
		en.pop(a); en.pop(b); en.pop(c); en.pop(d);

		if (IsVoid<RetT>::value) {
			(*func)(a, b, c, d);
			return 0;
		}
		else {
			en.push((*func)(a, b, c, d));
			return 1;
		}
	}

	template <class FunT, class RetT,
		class A, class B, class C>
	static int __cdecl cCall(lua_State * state) {
		auto func = reinterpret_cast<FunT *>(lua_touserdata(state, lua_upvalueindex(1)));

		Engine en(state);
		A a; B b; C c;
		en.pop(a); en.pop(b); en.pop(c);

		if (IsVoid<RetT>::value) {
			(*func)(a, b, c);
			return 0;
		}
		else {
			en.push((*func)(a, b, c));
			return 1;
		}
	}

	template <class FunT, class RetT,
		class A, class B>
	static int __cdecl cCall(lua_State * state) {
		auto func = reinterpret_cast<FunT *>(lua_touserdata(state, lua_upvalueindex(1)));

		Engine en(state);
		A a; B b;
		en.pop(a); en.pop(b);

		if (IsVoid<RetT>::value) {
			(*func)(a, b);
			return 0;
		}
		else {
			en.push((*func)(a, b));
			return 1;
		}
	}

	template <class FunT, class RetT,
		class A>
	static int __cdecl cCall(lua_State * state) {
		auto func = reinterpret_cast<FunT *>(lua_touserdata(state, lua_upvalueindex(1)));

		Engine en(state);
		A a;
		en.pop(a);

		if (IsVoid<RetT>::value) {
			(*func)(a);
			return 0;
		}
		else {
			en.push((*func)(a));
			return 1;
		}
	}

	template <class Funt, class RetT>
	static int __cdecl cCall(lua_State * state) {
		auto func = reinterpret_cast<FunT *>(lua_touserdata(state, lua_upvalueindex(1)));

		Engine en(state);

		if (IsVoid<RetT>::value) {
			(*func)();
			return 0;
		}
		else {
			en.push((*func)());
			return 1;
		}
	}

	template <class FunT>
	static int __cdecl deleteFunction(lua_State * state) {
		auto func = reinterpret_cast<FunT *>(lua_touserdata(state, 1));
		func->~FunT(); //just invoke destructor, actual memory is deleted by lua
		return 0;
	}

	template <class FunT>
	Function wrapFunction(FunT func, lua_CFunction cfunc) {
		new (lua_newuserdata(lua, sizeof(FunT))) FunT(func);
		lua_newtable(lua);

		lua_pushcfunction(lua, deleteFunction<FunT>);
		lua_setfield(lua, -2, "__gc");
		lua_setmetatable(lua, -2);

		lua_pushcclosure(lua, cfunc, 1);
		return Function(this);
	}
};

} //lua
} //spelprog2

#endif //LUASCRIPT_HPP
